
package org.chorus;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import org.chorus.DisplayUtils.LayoutType;
import org.gicentre.utils.move.ZoomPan;

import jwo.landserf.structure.RasterMap;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;
import processing.core.PImage;

//  **********************************************************************************
/** Simple display of one or more DEMs.
 *  @author Jo Wood
 *  @version 1.3.2, 18th March, 2013.
 */
//  **********************************************************************************

/* This file is part of the Chorus river bed analysis software package. Chorus is free software:
 * you can redistribute it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * Chorus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this
 * source code (see COPYING included with this source code). If not, see 
 * http://www.gnu.org/licenses/.
 */

public class DEMDisplay implements Modular, Comparable<Modular>
{
	// ------------------------ Object and class variables --------------------------

	private ArrayList<PImage> demImages;	// Graphical representations of DEMs.
	private ArrayList<String> titles;		// Titles of the DEMs
	private PApplet parent;					// Parent sketch in which this module is embedded.
	private PGraphics graphics;				// Graphics context for output.
	private int param;						// DEM parameter to display.
	private int colourOffset;				// Used to select starting palette colour
	private RasterMap legendRaster;			// Raster used to generate optional legend.
	private int order;						// Order number for sorted modules.
	private ZoomPan zoomer;					// For zooming into the DEM display.
	
	// ------------------------------- Constructors ---------------------------------
	
	/** Creates a DEM display. This version will display the DEM directly without any 
	 *  additional parameter overlaid.
	 */
	public DEMDisplay()
	{
		this(RasterMap.ELEV);
	}
	
	
	/** Creates a DEM display showing the given parameter type. 
	 *  @param param Surface parameter from RasterMap (e.g. ELEV, SLOPE, ASPECT etc.).
	 */
	public DEMDisplay(int param)
	{
		this.param = param;
		colourOffset = 0;
	}
	
	// ---------------------------------- Methods -----------------------------------

	/** Initialises the DEM display.
	 *  @param parent Parent class in which the DEM display is embedded.
	 */
	public void setup(PApplet parent)
	{
		this.parent = parent;
		this.graphics = parent.g;
		zoomer = new ZoomPan(parent,graphics);

		demImages = new ArrayList<PImage>();
		titles = new ArrayList<String>();
	}
	
	/** Sets the order number for this module. Allows a list of modules to be sorted in some arbitrary order.
	 *  @param order Order number where the smaller the number, the earlier in a sorted list the module will appear.
	 */
	public void setOrder(int order)
	{
		this.order = order;
	}
	
	/** Reports the order number for this module. Allows a list of modules to be sorted in some arbitrary order.
	 *  @return Order number where the smaller the number, the earlier in a sorted list the module will appear.
	 */
	public int getOrder()
	{
		return order;
	}
	
	/** Sets the the graphics context into which all output is directed. This method allows
	 *  output to be redirected to print output, offscreen buffers etc.
	 *  @param graphics New graphics context in which the modular class is embedded.
	 */
	public void setGraphics(PGraphics graphics)
	{
		this.graphics = graphics;
	}
	
	/** Would provide some text output that can be written to the given file but in this case does nothing.
	 *  @param fileName Name of output file (ignored).
	 *  @return True if output is written successfully but in this case always returns false since no output is written.
	 */
	public boolean writeOutput(String fileName)
	{
		// Do nothing.
		return false;
	}

	/** Draws the DEMs that have been added to this collection for display..
	 *  @param xOrigin Left-hand pixel coordinate of the area in which to draw the DEMs.
	 *  @param yOrigin Top pixel coordinate of the area in which to draw the DEMs.
	 *  @param width Width in pixels of the area in which to draw the DEMs.
	 *  @param height Height in pixels of the area in which to draw the DEMs.
	 */
	public void draw(float xOrigin, float yOrigin, float width, float height)
	{
		// Check to see that we have at least one DEM to display		
		if (demImages.size() == 0)
		{
			graphics.fill(graphics.color(80));
			graphics.textFont(DisplayUtils.mediumFont);
			graphics.textAlign(PConstants.CENTER, PConstants.CENTER);
			graphics.text("No DEMs available for display",xOrigin+width/2,yOrigin+height/2);
			return;
		}
					
		zoomer.transform();
		
		float aspectRatio = 1f;
		if (demImages.get(0) != null)
		{
			aspectRatio = (float)(demImages.get(0).width)/demImages.get(0).height;
		}
		
		// If we have very tall-thin DEMs, arrange in columns, if very wide-short, arrange in rows, otherwise in a grid.
		LayoutType layout = LayoutType.GRID;
		if (aspectRatio < 0.5)
		{
			layout = LayoutType.COLUMN;
		}
		else if (aspectRatio > 2)
		{
			layout = LayoutType.ROW;
		}
		
		Rectangle2D.Float[] rects = DisplayUtils.getBoundaries(xOrigin, yOrigin, width, height, demImages.size(), aspectRatio, layout);
		graphics.imageMode(PConstants.CENTER);
		
		for (int i=0; i< demImages.size(); i++)
		{
			PImage img = demImages.get(i);	
			if (img == null)
			{
				if (legendRaster != null)
				{
					if (layout == LayoutType.ROW)
					{
						float scaling = Math.min((rects[i].width-10)/100,(rects[i].height-10)/50);
						DisplayUtils.drawLegend(graphics, legendRaster, rects[i].x + rects[i].width/2,rects[i].y + rects[i].height/2,100*scaling,20*scaling, layout);
					}
					else
					{
						float scaling = Math.min((rects[i].width-10)/50,(rects[i].height-10)/100);
						DisplayUtils.drawLegend(graphics, legendRaster, rects[i].x + rects[i].width/2,rects[i].y + rects[i].height/2,20*scaling,100*scaling, layout);
					}
				}
				continue;
			}
			if (demImages.size() == 1)
			{
				graphics.fill(0);
			}
			else
			{
				graphics.fill(DisplayUtils.getCategoryPalette().findColour(colourOffset+i));
			}
			graphics.textFont(DisplayUtils.mediumFont);
			float textHeight = graphics.textAscent()+graphics.textDescent();
			float xPos,yPos,imageScaling,textScaling;
			
			if (layout == LayoutType.ROW)
			{
				// Text displayed to the left of image when in rows
				xPos = rects[i].x +textHeight/2;
				yPos = rects[i].y + (rects[i].height+textHeight)/2;
				imageScaling = Math.min((rects[i].width-10)/img.width,(rects[i].height-10)/img.height);
				textScaling = Math.min(1,(rects[i].width-img.width*imageScaling)/(3*graphics.textWidth(titles.get(i))));
				graphics.image(img,rects[i].x + rects[i].width/2,rects[i].y + rects[i].height/2,img.width*imageScaling,img.height*imageScaling);
			}
			else
			{
				// Text displayed below image.
				imageScaling = Math.min((rects[i].width-10)/img.width,(rects[i].height-10-textHeight)/img.height);
				xPos = rects[i].x+textHeight/2;
				yPos = rects[i].y + (rects[i].height+img.height*imageScaling+textHeight)/2;
				textScaling = Math.min(1,(rects[i].width-textHeight)/graphics.textWidth(titles.get(i)));
				graphics.image(img,rects[i].x + rects[i].width/2,rects[i].y + rects[i].height/2-textHeight/2,img.width*imageScaling,img.height*imageScaling);
			}

			// Show title of DEM, scaled if necessary to fit within display bounds.
			graphics.pushMatrix();
			graphics.translate(xPos,yPos);
			graphics.scale(textScaling,textScaling);
						
			graphics.textAlign(PConstants.LEFT,PConstants.BOTTOM);
			graphics.text(titles.get(i),0,0);
			graphics.popMatrix();
		}
	}
	
	/** Sets the colour palette start colour for text display.
	 *  @param offset Position in default colour palette in which the first DEM text is displayed.
	 */
	public void setColourOffset(int offset)
	{
		this.colourOffset = offset;
	}
	
	/** Would respond to mouse press events but does nothing in this case.
	 */
	public void mousePressed()
	{
		// No mouse events handled.
	}

	/** Would respond to mouse release events but does nothing in this case.
	 */
	public void mouseReleased()
	{
		// No mouse events handled.
	}

	/** Would respond to mouse drag events but does nothing in this case.
	 */
	public void mouseDragged()
	{
		// No mouse events handled.
	}
	
	/** Adds the given DEM to those which are displayed.
	 *  @param dem DEM to add.
	 */
	public void addDEM(RasterMap dem) 
	{
		if (dem == null)
		{
			demImages.add(null);
			titles.add("");
		}
		else
		{
			demImages.add(DisplayUtils.createImage(parent, dem, param));
			if (param == RasterMap.ELEV)
			{
				titles.add(dem.getHeader().getTitle());	
			}
			else
			{
				titles.add(RasterMap.getTypeName(param)+": "+dem.getHeader().getTitle());
			}
		}
	}
	
	/** Sets the raster to be used to generate the legend. If not supplied, no legend is drawn.
	 *  @param legendRaster Raster used to construct legend.
	 */
	public void setLegendRasterM(RasterMap legendRaster) 
	{
		this.legendRaster = legendRaster;
	}
	
	/** Allows this module to be compared with another to determine sort order.
	 *  @param otherModule Other module to compare with this one.
	 */
	public int compareTo(Modular otherModule)
	{
		int comparison = order-otherModule.getOrder();
		if (comparison == 0)
		{
			return hashCode()-otherModule.hashCode();
		}
		return comparison;
	}
}
